/**
* Project: yui3-common-mybatisx
 * Class MybatisMapperResultMapBuilder
 * Version 1.0
 * File Created at 2019年1月3日
 * $Id$
 * author yuyi
 * email 1060771195@qq.com
 */
package yui.comn.mybatisx.core;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.ibatis.annotations.Lang;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;

import yui.comn.mybatisx.annotation.Link;
import yui.comn.mybatisx.annotation.OneToMany;
import yui.comn.mybatisx.annotation.OneToOne;
import yui.comn.mybatisx.annotation.model.Clazz;
import yui.comn.mybatisx.annotation.model.JoinType;
import yui.comn.mybatisx.core.enums.SoftSqlMethod;
import yui.comn.mybatisx.core.exceptions.MybatisLinkException;
import yui.comn.mybatisx.core.toolkit.ClazzUtils;
import yui.comn.mybatisx.core.toolkit.MybatisLinkConstants;

/**
 * <p>
 * 通过注解生成resultMap，以及列表查询方法和行数查询方法
 * <p>
 * @author yuyi (1060771195@qq.com)
 */
public class InjectorMapperAnnotationAssistant {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final Configuration configuration;
    private final MapperBuilderAssistant assistant;
    private final Class<?> mapperClass;
    private final Class<?> retrunTypeClass;
    private final Class<?> modelTypeClass;
    
    public InjectorMapperAnnotationAssistant(Configuration configuration,
                                         MapperBuilderAssistant assistant,
                                         Class<?> mapperClass) {
        this.configuration = configuration;
        this.assistant = assistant;
        this.mapperClass = mapperClass;
        this.retrunTypeClass = extractReturnTypeClass(mapperClass);
        this.modelTypeClass = extractModelClass(mapperClass);
    }
    
    public String parseResultMapp(Class<?>... modelClazzs) {
        if (null == modelClazzs) {
            throw new MybatisLinkException("modelClazzs is null");
        }
        StringBuffer resultMapSimpleName = new StringBuffer();
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        
        for (Class<?> modelClass : modelClazzs) {
            applyResultMaps(resultMappings, getTableInfo(modelClass), null, null);
            if (resultMapSimpleName.length() > 0) {
                resultMapSimpleName.append(StringPool.UNDERSCORE).append(modelClass.getSimpleName());
            }
            resultMapSimpleName.append(modelClass.getSimpleName());
            /*resultMapSimpleName.append(modelClass.getSimpleName());
            List<Field> fields = ReflectionKit.getFieldList(ClassUtils.getUserClass(modelClass));
            for (Field field : fields) {
                applyResultMaps(resultMappings, modelClass, field, null);
            }*/
        }
        String resultMapId = mapperClass.getName() + StringPool.DOT + resultMapSimpleName + StringPool.DASH + "ResultMap";
        assistant.addResultMap(resultMapId, retrunTypeClass, null, null, resultMappings, null);
        
        return resultMapId;
    }
    
    public void parse(Link link, Method method) {
        Class<?> returnType = method.getReturnType();
        if (returnType != Integer.class && returnType != Long.class) {
            if (link.printRm()) {
                logger.info(method.getName() + "--resultMap");
            }
            //查询列表resultMap
            parseResultMap(link, method);
            if (link.print()) {
                logger.info(method.getName() + "--sql");
            }
            //查询列表
            parseListSql(link, method);
        } else {
            //查询条数
            parseCountSql(link, method);
        }
    }
    
    public void parseCountSql(Link link, Method method) {
        OneToOne[] ones = link.ones();
        String sqlTableRel = sqlTableRel(ones, modelTypeClass, true, true);
        String sqlWhere = sqlWhere(ones, modelTypeClass, true, false);
        String sql = String.format(SoftSqlMethod.DYNAMIC_COUNT.getSql(), sqlFirst(), sqlCount(), sqlTableRel, sqlWhere, sqlComment());
        
        if (link.print()) {
            logger.info(method.getName() + "-count-\n" + sql);
        }
        
        LanguageDriver languageDriver = getLanguageDriver(method);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Wrapper.class);
        // String id = mapperClass.getName() + StringPool.DOT + method.getName();
        String id = method.getName();
        
        addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null, 
                null, Integer.class, new NoKeyGenerator(), null, null, languageDriver);
    }
    
    public void parseListSql(Link link, Method method) {
        OneToOne[] ones = link.ones();
        String sqlSelectColumn = sqlSelectColumn(ones, modelTypeClass, true);
        String sqlTableRel = sqlTableRel(ones, modelTypeClass, true, true);
        String sqlWhere = sqlWhere(ones, modelTypeClass, true, true);
        String sql = String.format(SoftSqlMethod.DYNAMIC_LIST.getSql(), sqlFirst(), sqlSelectColumn,
                sqlTableRel, sqlWhere, sqlComment());
    
        if (link.print()) {
            logger.info(method.getName() + "-ones-\n" + sql);
        }
        
        LanguageDriver languageDriver = getLanguageDriver(method);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Wrapper.class);
        // String id = mapperClass.getName() + StringPool.DOT + method.getName();
        String id = method.getName();
        String resultMap = getLinkResultMapId(link, method);
        
        addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null, 
                resultMap, null, new NoKeyGenerator(), null, null, languageDriver);
        
        parseManySql(link, method);
    }
    
    public void parseManySql(Link link, Method method) {
        if (link.print()) {
            logger.info(method.getName() + "many debug breakpoint");
        }
        
        OneToMany[] manys = link.manys();
        if (ArrayUtils.isNotEmpty(manys)) {
            for (OneToMany many : manys) {
                Class<?> rightClass = many.rightClass();
                if (rightClass == Clazz.class) {
                    rightClass = extractModelClassFromOfTypeClass(many.ofTypeClass());
                    if (null == rightClass) {
                        throw new MybatisLinkException("OneToMany 没有对应的  实体类");
                    }
                }
                OneToOne[] ones2 = many.ones();
                OneToOne[] ones = new OneToOne[ones2.length + 1];
                ones[0] = getOneFromMany(many, link);
                if (ArrayUtils.isNotEmpty(ones2)) {
                    for (int i = 0; i < ones2.length; i++) {
                        ones[i+1] = ones2[i];
                    }
                }
                
                String sqlSelectColumn = sqlSelectColumn(ones, rightClass, false);
                String sqlTableRel = sqlTableRel(ones, rightClass, false, false);
                String sqlWhere = sqlWhere(ones, rightClass, false, false);
                String sql = String.format(SoftSqlMethod.DYNAMIC_LIST.getSql(), "", sqlSelectColumn,
                        sqlTableRel, sqlWhere, "");
                
                if (link.print()) {
                    logger.info(method.getName() + "-Many-\n" + sql);
                }
                
                LanguageDriver languageDriver = getLanguageDriver(method);
                SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Wrapper.class);
                // String id = StringPool.UNDERSCORE + many.property();
                String id = getManySelectId(many, method);
                String resultMap = getManyResultMapId(many, method);
                
                addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null, 
                        resultMap, null, new NoKeyGenerator(), null, null, languageDriver);
            }    
        }
    }
    
    private OneToOne getOneFromMany(OneToMany many, Link link) {
        final Class<?> leftClass = many.leftClass() != Clazz.class ? many.leftClass() : modelTypeClass;
        
        return new OneToOne() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return OneToOne.class;
            }
            
            @Override
            public String rightColumn() {
                return many.rightColumn();
            }
            
            @Override
            public Class<?> rightClass() {
                if (many.rightClass() != Clazz.class) {
                    return many.rightClass();
                } else {
                    return extractModelClassFromOfTypeClass(many.ofTypeClass());
                }
            }
            
            @Override
            public String rightAlias() {
                return null;
            }
            
            @Override
            public String onArgName() {
                return null;
            }
            
            @Override
            public String leftColumn() {
                return many.leftColumn();
            }
            
            @Override
            public Class<?> leftClass() {
                return leftClass;
            }
            
            @Override
            public String leftAlias() {
                return many.leftAlias();
            }
            
            @Override
            public JoinType joinType() {
                return JoinType.NONE;
            }
        };
    }
    
    /**
     * 添加 MappedStatement 到 Mybatis 容器
     */
    protected MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource,
                                                 SqlCommandType sqlCommandType, Class<?> parameterClass,
                                                 String resultMap, Class<?> resultType, KeyGenerator keyGenerator,
                                                 String keyProperty, String keyColumn, LanguageDriver languageDriver) {
        String statementName = mapperClass.getName() + StringPool.DOT + id;
        if (hasMappedStatement(statementName)) {
            System.err.println(StringPool.LEFT_BRACE + statementName + "} Has been loaded by XML or SqlProvider, ignoring the injection of the SQL.");
            return null;
        }
        /* 缓存逻辑处理 */
        boolean isSelect = false;
        if (sqlCommandType == SqlCommandType.SELECT) {
            isSelect = true;
        }
        return assistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, null, null, null,
            parameterClass, resultMap, resultType, null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn,
            configuration.getDatabaseId(), languageDriver, null);
    }
    
    /**
     * 是否已经存在MappedStatement
     *
     * @param mappedStatement MappedStatement
     * @return true or false
     */
    private boolean hasMappedStatement(String mappedStatement) {
        return configuration.hasStatement(mappedStatement, false);
    }
    
    private String sqlWhere(OneToOne[] ones, Class<?> modelType, boolean sqlScriptFlag, boolean sqlIdFlag) {
        StringBuffer where = new StringBuffer();
        
        Set<String> classAliasSet = new HashSet<>();
        
        String modelTypeAlias = "";
        if (ArrayUtils.isNotEmpty(ones)) {
            for (OneToOne one : ones) {
                Class<?> leftClass = one.leftClass() != Clazz.class ? one.leftClass() : modelType;
                Class<?> rightClass = one.rightClass();
                String leftAlias = getTableAlias(leftClass, one.leftAlias());
                String rightAlias = getTableAlias(rightClass, one.rightAlias());
                //主键查询是，主键的alias + 字段
                if (StringUtils.isBlank(modelTypeAlias)) {
                    if (leftClass == modelTypeClass) {
                        modelTypeAlias = leftAlias;
                    } else if (rightClass == modelTypeClass) {
                        modelTypeAlias = rightAlias;
                    }
                }
                String leftColumn = one.leftColumn();
                if (StringUtils.isBlank(leftColumn)) {
                    //默认是主键
                    leftColumn = getTableInfo(ClassUtils.getUserClass(leftClass)).getKeyColumn();
                }
                String rightColumn = one.rightColumn();
                if (StringUtils.isBlank(rightColumn)) {
                    //默认是主键
                    rightColumn = getTableInfo(ClassUtils.getUserClass(rightClass)).getKeyColumn();
                }
                
                JoinType joinType = one.joinType();
                
                //右连接 转 左连接 只对两表联查的数据进行反转
                if (joinType == JoinType.RIGHT && ones.length == 1) {
                    Class<?> leftClassTemp = leftClass;
                    String leftAliasTemp = leftAlias;
                    String leftColumnTemp = leftColumn;
                    
                    leftClass = rightClass;
                    leftAlias = rightAlias;
                    leftColumn = rightColumn;
                    
                    rightClass = leftClassTemp;
                    rightAlias = leftAliasTemp;
                    rightColumn = leftColumnTemp;
                    
                    joinType = JoinType.LEFT;
                }
                
                
                if (joinType != JoinType.NONE) {
                    if (!classAliasSet.contains(leftAlias)) {
                        classAliasSet.add(leftAlias);
                        if (joinType == JoinType.LEFT || joinType == JoinType.INNER) {
                            TableInfo tableInfo = getTableInfo(ClassUtils.getUserClass(leftClass));
                            if (tableInfo.isWithLogicDelete()) {
                                where.append(" AND ").append(leftAlias).append(StringPool.DOT).append(tableInfo.getLogicDeleteSql(false, true));
                            }
                            //where.append((tableInfo.getLogicDeleteMapSql(leftAlias, true, false) + StringPool.NEWLINE));
                        }
                    }
                }
                
                if (!classAliasSet.contains(rightAlias)) {
                    classAliasSet.add(rightAlias);
                    if (joinType != JoinType.LEFT) {
                        TableInfo tableInfo = getTableInfo(ClassUtils.getUserClass(rightClass));
                        if (tableInfo.isWithLogicDelete()) {
                            where.append(" AND ").append(rightAlias).append(StringPool.DOT).append(tableInfo.getLogicDeleteSql(false, true));
                        }
                        //where.append((tableInfo.getLogicDeleteMapSql(rightAlias, true, false) + StringPool.NEWLINE));
                    }
                }
                
                if (joinType == JoinType.INNER) {
                    where.append(StringPool.SPACE).append(StringPool.AND).append(StringPool.SPACE);
                    where.append(leftAlias).append(StringPool.DOT).append(leftColumn);
                    where.append(StringPool.EQUALS);
                    where.append(rightAlias).append(StringPool.DOT).append(rightColumn);
                }
                
                if (joinType == JoinType.NONE) {
                    where.append(StringPool.SPACE).append(StringPool.AND).append(StringPool.SPACE);
                    where.append(rightAlias).append(StringPool.DOT).append(rightColumn);
                    
                    where.append(StringPool.SPACE).append(StringPool.EQUALS).append(StringPool.SPACE)
                        .append(StringPool.HASH_LEFT_BRACE).append(leftAlias)
                        .append(StringPool.UNDERSCORE + StringPool.UNDERSCORE).append(leftColumn)
                        .append(StringPool.RIGHT_BRACE)
                        .append(StringPool.NEWLINE);
                }
            }
        } else {
            //处理没有一对一, 但是又一对多
            Class<?> leftClass =  modelTypeClass;
            String leftAlias = getTableAlias(leftClass, "");
            modelTypeAlias = leftAlias;
            TableInfo tableInfo = getTableInfo(ClassUtils.getUserClass(leftClass));
            if (tableInfo.isWithLogicDelete()) {
                where.append(" AND ").append(leftAlias).append(StringPool.DOT).append(tableInfo.getLogicDeleteSql(false, true));
            }
        }
        if (!sqlScriptFlag) {
            String sqlScript = SqlScriptUtils.convertTrim(where.toString(), "WHERE", null, "AND|OR", null);
            return sqlScript;
        }
      
        //分离前面的信息，用户后面添加if ew != null 
        String sqlScript = "";
        
        if (sqlIdFlag) {
            //通过主键查询
            TableInfo tableInfo = getTableInfo(ClassUtils.getUserClass(modelTypeClass));
            //通过主键查询
            String sqlId = " AND " + modelTypeAlias + StringPool.DOT + tableInfo.getKeyColumn() + " = ${%s}";
            sqlScript += SqlScriptUtils.convertIf(String.format(sqlId, Constants.WRAPPER_DOT + MybatisLinkConstants.ID), 
                    String.format("%s != null", Constants.WRAPPER_DOT + MybatisLinkConstants.ID,
                            Constants.WRAPPER_DOT + MybatisLinkConstants.ID), true);
            sqlScript += StringPool.NEWLINE;
            //通过租户ID查询
//            if (null != TableUtils.getTenantId(modelClass)) {
//                String sqlTntId = " AND " + modelTypeAlias + StringPool.DOT + TableUtils.getTenantId(modelClass) + " = ${%s}";
//                sqlScript += SqlScriptUtils.convertIf(String.format(sqlTntId, Constants.WRAPPER_DOT + MybatisxConstants.TNT_ID), 
//                        String.format("%s != null", Constants.WRAPPER_DOT + MybatisxConstants.TNT_ID,
//                                Constants.WRAPPER_DOT + MybatisxConstants.TNT_ID), true);
//                sqlScript += StringPool.NEWLINE;
//            }
        }
        
        /*sqlScript += SqlScriptUtils.convertIf(SqlScriptUtils.convertTrim(String.format(" ${%s}", Constants.WRAPPER_SQLSEGMENT), "AND", null, "AND", null),
                String.format("%s != null and %s != ''", Constants.WRAPPER_SQLSEGMENT,
                        Constants.WRAPPER_SQLSEGMENT), true);*/
        sqlScript += SqlScriptUtils.convertIf(String.format(" AND ${%s}", Constants.WRAPPER_SQLSEGMENT),
                String.format("%s != null and %s != '' and %s", Constants.WRAPPER_SQLSEGMENT, Constants.WRAPPER_SQLSEGMENT,
                        Constants.WRAPPER_NONEMPTYOFWHERE), true);
        sqlScript += StringPool.NEWLINE;
        // sqlScript = SqlScriptUtils.convertWhere(sqlScript) + NEWLINE;
        sqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", Constants.WRAPPER_SQLSEGMENT),
            String.format("%s != null and %s != '' and %s", Constants.WRAPPER_SQLSEGMENT, Constants.WRAPPER_SQLSEGMENT,
                    Constants.WRAPPER_EMPTYOFWHERE), true);
        
        sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", Constants.WRAPPER), true);
        
        sqlScript = SqlScriptUtils.convertTrim(where + StringPool.NEWLINE + sqlScript, "WHERE", null, "AND|OR", null);
        
        return sqlScript;
    }
    
    private String sqlTableRel(OneToOne[] ones, Class<?> modelClass, boolean SqlScriptFlag, boolean dynamicTableFlag) {
        Set<String> classAliasSet = new HashSet<>();
        
        StringBuffer tableRel = new StringBuffer();
        
        // String tableName = getTableName(modelTypeClass);
        // tableRel.append(tableName).append(StringPool.SPACE).append(tableName);
        if (ArrayUtils.isNotEmpty(ones)) {
            for (OneToOne one : ones) {
                Class<?> leftClass = one.leftClass() != Clazz.class ? one.leftClass() : modelClass;
                Class<?> rightClass = one.rightClass();
                String leftAlias = getTableAlias(leftClass, one.leftAlias());
                String rightAlias = getTableAlias(rightClass, one.rightAlias());
                
                if (!classAliasSet.isEmpty()) {
                    if (!classAliasSet.contains(leftAlias) && !classAliasSet.contains(rightAlias)) {
                        throw new MybatisLinkException("OneToOne 排序有问题， left=" + leftAlias + ", right=" + rightAlias);
                    }
                }
                
                String leftColumn = one.leftColumn();
                if (StringUtils.isBlank(leftColumn)) {
                    //默认是主键
                    leftColumn = getTableInfo(ClassUtils.getUserClass(leftClass)).getKeyColumn();
                }
                String rightColumn = one.rightColumn();
                if (StringUtils.isBlank(rightColumn)) {
                    //默认是主键
                    rightColumn = getTableInfo(ClassUtils.getUserClass(rightClass)).getKeyColumn();
                }
                
                JoinType joinType = one.joinType();
                
                //右连接 转 左连接 只对两表联查的数据进行反转
                if (joinType == JoinType.RIGHT && ones.length == 1) {
                    Class<?> leftClassTemp = leftClass;
                    String leftAliasTemp = leftAlias;
                    String leftColumnTemp = leftColumn;
                    
                    leftClass = rightClass;
                    leftAlias = rightAlias;
                    leftColumn = rightColumn;
                    
                    rightClass = leftClassTemp;
                    rightAlias = leftAliasTemp;
                    rightColumn = leftColumnTemp;
                    
                    joinType = JoinType.LEFT;
                }
                
                boolean joinTypeUse = false;
                if (joinType != JoinType.NONE) {
                    if (!classAliasSet.contains(leftAlias)) {
                        // a - b 
                        // c - a 兼容这种形式
                        if (!classAliasSet.isEmpty()) {
                            tableRel.append(getReverseJoinTypeKey(joinType));
                            joinTypeUse = true;
                        }
                        
                        classAliasSet.add(leftAlias);
                        // tableRel.append(getTableName(leftClass)).append(StringPool.SPACE).append(leftAlias);
                        tableRel.append(getDynamicTableNameAndAlias(leftClass, leftAlias, dynamicTableFlag));
                        
                    }
                    if (!joinTypeUse) {
                        tableRel.append(joinType.getKey());
                        joinTypeUse = true;
                    }
                }
                
                if (!classAliasSet.contains(rightAlias)) {
                    classAliasSet.add(rightAlias);
                    // tableRel.append(getTableName(rightClass)).append(StringPool.SPACE).append(rightAlias);
                    tableRel.append(getDynamicTableNameAndAlias(rightClass, rightAlias, dynamicTableFlag));
                }
                
                if (joinType != JoinType.INNER && joinType != JoinType.NONE) {
                    tableRel.append(StringPool.SPACE).append(StringPool.ON).append(StringPool.SPACE);
                    tableRel.append(leftAlias).append(StringPool.DOT).append(leftColumn);
                    tableRel.append(StringPool.EQUALS);
                    tableRel.append(rightAlias).append(StringPool.DOT).append(rightColumn);
                    if (joinType == JoinType.LEFT) {
                        TableInfo tableInfo = getTableInfo(ClassUtils.getUserClass(rightClass));
                        if (tableInfo.isWithLogicDelete()) {
                            tableRel.append(" AND ").append(rightAlias).append(StringPool.DOT).append(tableInfo.getLogicDeleteSql(false, true));
                        }
                    } else if (joinType == JoinType.RIGHT) {
                        TableInfo tableInfo = getTableInfo(ClassUtils.getUserClass(leftClass));
                        if (tableInfo.isWithLogicDelete()) {
                            tableRel.append(" AND ").append(leftAlias).append(StringPool.DOT).append(tableInfo.getLogicDeleteSql(false, true));
                        }
                    }
                    
                    if (StringUtils.isNotBlank(one.onArgName())) {
                        tableRel.append(StringPool.SPACE);
                        String sqlOnMap = Constants.WRAPPER_DOT + MybatisLinkConstants.SQL_ON_MAP;
                        String onArg = sqlOnMap + Constants.DOT + one.onArgName();
                        String ifTest = String.format("%s != null and %s != null and %s != null and %s != ''", 
                                Constants.WRAPPER, sqlOnMap, onArg, onArg);
                        String sqlScript = " AND ${ew." + MybatisLinkConstants.SQL_ON_MAP + "." + one.onArgName() + "}";
                        String convertIf = SqlScriptUtils.convertIf(sqlScript, ifTest, true);
                        tableRel.append(convertIf);
                    }
                }
            }
        } else {
            //处理没有一对一, 但是又一对多
            Class<?> leftClass =  modelClass;
            String leftAlias = getTableAlias(leftClass, "");
            // tableRel.append(getTableName(leftClass)).append(StringPool.SPACE).append(leftAlias);
            tableRel.append(getDynamicTableNameAndAlias(leftClass, leftAlias, dynamicTableFlag));
        }
        
        return tableRel.toString();
    }
    
    private String getTableAlias(Class<?> clazz, String alias) {
        if (StringUtils.isNotBlank(alias)) {
            return alias;
        } else {
            return getTableName(clazz);
        }
    }
    
    protected String sqlFirst() {
        return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null", Constants.WRAPPER, Constants.Q_WRAPPER_SQL_FIRST),
            SqlScriptUtils.unSafeParam(Constants.Q_WRAPPER_SQL_FIRST), Constants.EMPTY);
    }
    
    protected String sqlComment() {
        return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null", Constants.WRAPPER, Constants.Q_WRAPPER_SQL_COMMENT),
            SqlScriptUtils.unSafeParam(Constants.Q_WRAPPER_SQL_COMMENT), Constants.EMPTY);
    }
    
    protected String sqlCount() {
        return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null", Constants.WRAPPER, Constants.Q_WRAPPER_SQL_SELECT),
            SqlScriptUtils.unSafeParam(Constants.Q_WRAPPER_SQL_SELECT), Constants.ASTERISK);
    }
    
    private String sqlSelectColumn(OneToOne[] ones, Class<?> modelClass, boolean SqlScriptFlag) {
        Set<String> classAliasSet = new HashSet<>();
        StringBuffer selectColumns = new StringBuffer();
        // TableInfo tableInfo = configuration.getTableInfo(ClassUtils.getUserClass(modelClass));
        
        // selectColumns.append(tableInfo.getAllMapSqlSelect());
        
        if (ArrayUtils.isNotEmpty(ones)) {
            for (OneToOne one : ones) {
                Class<?> leftClass = one.leftClass() != Clazz.class ? one.leftClass() : modelClass;
                Class<?> rightClass = one.rightClass();
                String leftAlias = getTableAlias(leftClass, one.leftAlias());
                String rightAlias = getTableAlias(rightClass, one.rightAlias());
                
                if (one.joinType() != JoinType.NONE) {
                    if (!classAliasSet.contains(leftAlias)) {
                        classAliasSet.add(leftAlias);
                        sqlSelectColumn(selectColumns, leftClass, leftAlias);
                    }
                }
                
                if (!classAliasSet.contains(rightAlias)) {
                    classAliasSet.add(rightAlias);
                    sqlSelectColumn(selectColumns, rightClass, rightAlias);
                }
            }
        } else {
            Class<?> leftClass =  modelClass;
            String leftAlias = getTableAlias(leftClass, "");
            sqlSelectColumn(selectColumns, leftClass, leftAlias);
        }
        if (!SqlScriptFlag) {
            return selectColumns.toString();
        }
        return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null",
                Constants.WRAPPER, Constants.Q_WRAPPER_SQL_SELECT),
                SqlScriptUtils.unSafeParam(Constants.Q_WRAPPER_SQL_SELECT), selectColumns.toString());
    }
    
    private void sqlSelectColumn(StringBuffer selectColumns, Class<?> clazz, String alias) {
        StringBuffer scs = new StringBuffer();
        TableInfo tableInfo = getTableInfo(ClassUtils.getUserClass(clazz));
        String tableAlias = getTableAlias(clazz, alias);
        
        String allSqlSelect = tableInfo.getAllSqlSelect();
        String[] sqlSelects = allSqlSelect.split(StringPool.COMMA);
        for (String sqlSelect : sqlSelects) {
            if (scs.length() > 0) {
                scs.append(StringPool.COMMA);
            }
            scs.append(StringPool.SPACE).append(tableAlias)
                    .append(StringPool.DOT)
                    // .append(StringPool.BACKTICK)
                    .append(sqlSelect)
                    // .append(StringPool.BACKTICK)
                    .append(StringPool.SPACE)
                    .append(tableAlias).append(StringPool.UNDERSCORE + StringPool.UNDERSCORE).append(sqlSelect)
                    .append(StringPool.SPACE);
        }
        
        if (selectColumns.length() > 0) {
            selectColumns.append(StringPool.COMMA);
        }
        selectColumns.append(scs);
    }
    
    public String parseResultMap(Link link, Method method) {
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        // Class<?> modelClass = extractModelClass(mapperClass);
        
        OneToOne[] ones = link.ones();
        //如果一对一 和 一对多 都是空，抛出错误
        if (ones.length == 0 && link.manys().length == 0) {
            throw new MybatisLinkException("一对一 和 一对多 不能都为空");
        }
        
        if (ones.length == 0) {
            Class<?> leftClass = modelTypeClass;
            String leftAlias = getTableAlias(leftClass, "");
            parseResultMaps(resultMappings, leftClass, leftAlias, new HashSet<>(), null);
        }
        
        if (ArrayUtils.isNotEmpty(ones)) {
            Set<String> classAliasSet = new HashSet<>();
            // parseResultMaps(resultMappings, modelTypeClass, null, classAliasSet);
            for (OneToOne one : ones) {
                Class<?> leftClass = one.leftClass() != Clazz.class ? one.leftClass() : modelTypeClass;
                Class<?> rightClass = one.rightClass();
                String leftAlias = getTableAlias(leftClass, one.leftAlias());
                String rightAlias = getTableAlias(rightClass, one.rightAlias());
                
                parseResultMaps(resultMappings, leftClass, leftAlias, classAliasSet, null);
                parseResultMaps(resultMappings, rightClass, rightAlias, classAliasSet, null);
            }
        }
        
        OneToMany[] manys = link.manys();
        if (ArrayUtils.isNotEmpty(manys)) {
            for (OneToMany oneToMany : manys) {
                parseResultMaps(resultMappings, oneToMany, method);
                parseResultMap(oneToMany, method, link);
            }
        }
        
        String resultMapId = getLinkResultMapId(link, method);
        assistant.addResultMap(resultMapId, retrunTypeClass, null, null, resultMappings, null);
        
        //resultMap日志打印
        if (null != link && link.printRm()) {
            StringBuffer resultMapLog = new StringBuffer();
            resultMapLog.append("\n<resultMap id=\""+ resultMapId +"\" type=\"" + retrunTypeClass + "\">\n");
            for (ResultMapping rm : resultMappings) {
                if (null == rm.getNestedQueryId()) {
                    resultMapLog.append("    <result property=\"" + rm.getProperty() + "\" column=\"" + rm.getColumn() + "\" jdbcType=\"" + rm.getJavaType() + "\"/>\n");
                } else {
                    resultMapLog.append("    <collection property=\"" + rm.getProperty() + "\" column=\"" + rm.getColumn() 
                    + "\" ofType=\"" + rm.getJavaType() + "\" select=\"" + rm.getNestedQueryId() + "\"/>\n");
                }
            }
            resultMapLog.append("</resultMap>");
            logger.info(resultMapLog.toString());
        }
        
        return resultMapId;
    }
    
    private void parseResultMaps(List<ResultMapping> resultMappings, OneToMany oneToMany, Method method) {
        Class<?> leftClass = oneToMany.leftClass() != Clazz.class ? oneToMany.leftClass() : modelTypeClass;
        String nestedSelect = StringPool.UNDERSCORE + method.getName() + StringPool.UNDERSCORE + oneToMany.property();
        
        String tableAlias = getTableAlias(leftClass, oneToMany.leftAlias());
        
        // String column = getColumn(leftClass, oneToMany.leftColumn());
        String column = tableAlias + StringPool.UNDERSCORE + StringPool.UNDERSCORE + oneToMany.leftColumn();
        //if (StringUtils.isNotBlank(oneToMany.leftAlias())) {
        //    column = oneToMany.leftAlias() + StringPool.UNDERSCORE + StringPool.UNDERSCORE + oneToMany.leftColumn();
        //}
        applyResultMaps(resultMappings, oneToMany.property(), column, null, null, 
        		nestedSelect, null, null, null, null);
    }
    
//    private void parseResultMaps(List<ResultMapping> resultMappings, OneToOne oneToOne, Set<String> classAliasSet) {
//        parseResultMaps(resultMappings, oneToOne.rightClass(), oneToOne.rightAlias(), classAliasSet);
//        if (oneToOne.leftClass() != Clazz.class) {
//            parseResultMaps(resultMappings, oneToOne.leftClass(), oneToOne.leftAlias(), classAliasSet);
//        }
//    }
        
    private void parseResultMaps(List<ResultMapping> resultMappings, Class<?> clazz, String alias, Set<String> classAliasSet, Class<?> resultType) {
        if (classAliasSet.contains(alias)) {
            return;
            //throw new MybatisxException("alias = " + alias + ", 已经存在");
        }
        applyResultMaps(resultMappings, getTableInfo(clazz), alias, resultType);
        
        
        /*List<Field> fields = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz));
        for (Field field : fields) {
            if (tableName.equals(alias)) {
                applyResultMaps(resultMappings, clazz, field, resultType);
            } else {
                String property = alias + StringPool.DOT + field.getName();
                // String column = alias + StringPool.UNDERSCORE + StringPool.UNDERSCORE + field.getName();
                String column = alias + StringPool.UNDERSCORE + StringPool.UNDERSCORE + StringUtils.camelToUnderline(field.getName());
                applyResultMaps(resultMappings, property, column, null, resultType);
            }
        }*/
    }
    
    private String parseResultMap(OneToMany many, Method method, Link link) {
        List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
        Set<String> classAliasSet = new HashSet<>();
        
        Class<?> rightManyClass = many.rightClass();
        if (rightManyClass == Clazz.class) {
            //默认DTO对应的VO
            rightManyClass = extractModelClassFromOfTypeClass(many.ofTypeClass());
            if (null == rightManyClass) {
                throw new MybatisLinkException("OneToMany 没有对应的  实体类");
            }
        }
        String rightManyAlias = getTableAlias(rightManyClass, many.rightAlias());
        
        parseResultMaps(resultMappings, rightManyClass, rightManyAlias, classAliasSet, many.ofTypeClass());
        
        OneToOne[] ones = many.ones();
        if (ArrayUtils.isNotEmpty(ones)) {
            if (ArrayUtils.isNotEmpty(ones)) {
                for (OneToOne one : ones) {
                    Class<?> leftClass = one.leftClass() != Clazz.class ? one.leftClass() : rightManyClass;
                    Class<?> rightClass = one.rightClass();
                    String leftAlias = getTableAlias(leftClass, one.leftAlias());
                    String rightAlias = getTableAlias(rightClass, one.rightAlias());
                    
                    parseResultMaps(resultMappings, leftClass, leftAlias, classAliasSet, many.ofTypeClass());
                    parseResultMaps(resultMappings, rightClass, rightAlias, classAliasSet, many.ofTypeClass());
                    
                    //parseResultMaps(resultMappings, oneToOne, classAliasSet);
                }
            }
        }
        
        String resultMapId = getManyResultMapId(many, method);
        // assistant.addResultMap(resultMapId, retrunTypeClass, null, null, resultMappings, null);
        assistant.addResultMap(resultMapId, many.ofTypeClass(), null, null, resultMappings, null);
        
        //resultMap日志打印
        if (null != link && link.printRm()) {
            StringBuffer resultMapLog = new StringBuffer();
            resultMapLog.append("\n<resultMap id=\""+ resultMapId +"\" type=\"" + many.ofTypeClass() + "\">\n");
            for (ResultMapping rm : resultMappings) {
                resultMapLog.append("    <result property=\"" + rm.getProperty() + "\" column=\"" + rm.getColumn() + "\" jdbcType=\"" + rm.getJavaType() + "\"/>\n");
            }
            resultMapLog.append("</resultMap>");
            logger.info(resultMapLog.toString());
        }
        
        return resultMapId;
    }
    
    private String getLinkResultMapId(Link link, Method method) {
        String resultMapId = null;
        if (StringUtils.isBlank(link.resultMapId())) {
            //如果resultMapId存在，先去查找conf中是否存在，没有在去创建
            resultMapId = mapperClass.getName() + StringPool.DOT + method.getName() + StringPool.DASH + "ResultMap";
        } else {
            resultMapId = mapperClass.getName() + StringPool.DOT + link.resultMapId() + StringPool.DASH + "ResultMap";
        }
        return resultMapId;
    }
    
    private String getManySelectId(OneToMany many, Method method) {
        return mapperClass.getName() + StringPool.DOT + StringPool.UNDERSCORE + method.getName() + StringPool.UNDERSCORE + many.property();
    }
    
    private String getManyResultMapId(OneToMany many, Method method) {
        return mapperClass.getName() + StringPool.DOT + StringPool.UNDERSCORE + method.getName() + StringPool.UNDERSCORE + many.property() + StringPool.DASH + "ResultMap";
    }
    
    private void applyResultMaps(List<ResultMapping> resultMappings, TableInfo tableInfo, String alias, Class<?> resultType) {
        String tableName = tableInfo.getTableName();
        String objName = StringUtils.firstToLowerCase(tableInfo.getEntityType().getSimpleName());
        if (!tableName.equals(alias) && StringUtils.isNotBlank(alias)) {
            objName = alias;
            tableName = alias;
        }
        // 主键字段
        applyResultMaps(resultMappings, getProperty(objName, tableInfo.getKeyProperty()), 
        		getColumn(tableName, tableInfo.getKeyColumn()), null, resultType, null, null, 
        		null, null, null);
        
        // 其他字段
        List<TableFieldInfo> fieldList = tableInfo.getFieldList();
        for (TableFieldInfo tableField : fieldList) {
            applyResultMaps(resultMappings, getProperty(objName, tableField.getProperty()), 
            		getColumn(tableName, tableField.getColumn()), tableField.getPropertyType(), 
            		resultType, null, null, null, null, tableField.getTypeHandler());
        }
    }
    
    private void applyResultMaps(List<ResultMapping> resultMappings, String property, String column, 
    		Class<?> javaType, Class<?> resultType, String nestedSelect, String nestedResultMap,
    		String notNullColumn, String columnPrefix, Class<? extends TypeHandler<?>> typeHandler) {
        ResultMapping resultMapping = assistant.buildResultMapping(
                null != resultType? resultType : retrunTypeClass,
                property,
                column,
                javaType,
                null,
                nestedSelect,
                nestedResultMap,
                notNullColumn,
                columnPrefix,
                typeHandler,
                null,
                null,
                null,
                false);
        resultMappings.add(resultMapping);
    }
    
    /*private void applyResultMaps(List<ResultMapping> resultMappings, TableInfo tableInfo, TableFieldInfo tableField, Class<?> resultType) {
        ResultMapping resultMapping = assistant.buildResultMapping(
                null != resultType? resultType : retrunTypeClass,
                        getProperty(tableInfo, tableField),
                        getColumn(tableInfo, tableField),
                        tableField.getPropertyType(),
                        null,
                        null,
                        null,
                        null,
                        null,
                        null,
                        null,
                        null,
                        null,
                        false);
        
        resultMappings.add(resultMapping);
    }*/
    /*private void applyResultMaps(List<ResultMapping> resultMappings, Class<?> modelClass, Field field, Class<?> resultType) {
        ResultMapping resultMapping = assistant.buildResultMapping(
                null != resultType? resultType : retrunTypeClass,
                getProperty(modelClass, field),
                getColumn(modelClass, field),
                field.getType(),
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                false);
        
        resultMappings.add(resultMapping);
    }*/
    
    public Class<?> extractModelClassFromOfTypeClass(Class<?> ofTypeClass) {
    	return ClazzUtils.extractModelClassFromOfTypeClass(ofTypeClass);
    }
    
    public Class<?> extractModelClass(Class<?> mapperClass) {
        Type[] types = extractTypes(mapperClass);
        return types == null ? null : (Class<?>) types[0];
    }
    
    public Class<?> extractReturnTypeClass(Class<?> mapperClass) {
        Type[] types = extractTypes(mapperClass);
        return types == null ? null : (Class<?>) types[1];
    }
    
    public Type[] extractTypes(Class<?> mapperClass) {
        Type[] types = mapperClass.getGenericInterfaces();
        ParameterizedType target = null;
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
                if (ArrayUtils.isNotEmpty(typeArray)) {
                    for (Type t : typeArray) {
                        if (t instanceof TypeVariable || t instanceof WildcardType) {
                            break;
                        } else {
                            target = (ParameterizedType) type;
                            break;
                        }
                    }
                }
                break;
            }
        }
        return target == null ? null : target.getActualTypeArguments();
    }
    
    /*private List<TableFieldInfo> getTableFieldInfoList(TableInfo tableInfo) {
        List<TableFieldInfo> fieldList = new ArrayList<>();
        TableFieldInfo idField = new TableFieldInfo();
        
        rtableInfo.getFieldList();
    }*/
    
    private String getProperty(String objName, String property) {
        return objName + StringPool.DOT + property;
    }
    
    private String getColumn(String tableName, String column) {
        return tableName + StringPool.UNDERSCORE + StringPool.UNDERSCORE + StringUtils.camelToUnderline(column);
    }
    
//    private String getProperty(Class<?> modelClass, String fieldName) {
//        return StringUtils.firstCharToLower(modelClass.getSimpleName()) + StringPool.DOT + fieldName;
//    }
    
//    private String getProperty(Class<?> modelClass, Field field) {
//        return StringUtils.firstCharToLower(modelClass.getSimpleName()) + StringPool.DOT + field.getName();
//    }
//    
//    private String getColumn(Class<?> modelClass, Field field) {
//        //TableInfo tableInfo = configuration.getTableInfo(ClassUtils.getUserClass(modelClass));
//        //String tableName = tableInfo.getTableName();
//        //return tableName + StringPool.UNDERSCORE + StringPool.UNDERSCORE + field.getName();
//        return getColumn(modelClass, field.getName());
//    }
    
//    private String getColumn(Class<?> modelClass, String fieldName) {
//        TableInfo tableInfo = getTableInfo(modelClass);
//        String tableName = tableInfo.getTableName();
//        fieldName = StringUtils.camelToUnderline(fieldName);
//        return tableName + StringPool.UNDERSCORE + StringPool.UNDERSCORE + fieldName;
//    }
    
    private String getTableName(Class<?> modelClass) {
        TableInfo tableInfo = getTableInfo(modelClass);
        return tableInfo.getTableName();
    }
    
    /**
     * 支持获取动态表名和别名
     * 例如 t_sys_user t_sys_user 
     */
    protected String getDynamicTableNameAndAlias(Class<?> modelClass, String alias, boolean dynamicTableFlag) {
        if (dynamicTableFlag) {
            return SqlScriptUtils.convertChoose(
                    String.format("%s != null and %s != null and %s != null", 
                            Constants.WRAPPER, 
                            Constants.WRAPPER_DOT + MybatisLinkConstants.TABLE_NAME_MAP, 
                            Constants.WRAPPER_DOT + MybatisLinkConstants.TABLE_NAME_MAP + Constants.DOT + alias), 
                    String.format("${%s} %s", Constants.WRAPPER_DOT + MybatisLinkConstants.TABLE_NAME_MAP + Constants.DOT + alias, alias), 
                    getTableInfo(modelClass).getTableName() + Constants.SPACE + alias);
        } else {
            return getTableInfo(modelClass).getTableName() + Constants.SPACE + alias;
        }
    }
    
    private LanguageDriver getLanguageDriver(Method method) {
        Lang lang = method.getAnnotation(Lang.class);
        Class<? extends LanguageDriver> langClass = null;
        if (lang != null) {
            langClass = lang.value();
        }
        return configuration.getLanguageDriver(langClass);
    }
    
    private String getReverseJoinTypeKey(JoinType joinType) {
        if (joinType == JoinType.LEFT) {
            return JoinType.RIGHT.getKey();
        } else if (joinType == JoinType.RIGHT) {
            return JoinType.LEFT.getKey();
        }
        return joinType.getKey();
    }
    
    private TableInfo getTableInfo(Class<?> modelClass) {
        TableInfo tableInfo = TableInfoHelper.getTableInfo(ClassUtils.getUserClass(modelClass));
        if (null == tableInfo) {
            // tableInfo = TableInfoHelper.initTableInfo(assistant, ClassUtils.getUserClass(modelClass));
            // throw new MybatisxException("model class 没有对应的 表信息");
            // logger.warn("tableInfo not find, please check this, modelClass=" + modelClass);
            /* 初始化表名相关 */
            tableInfo = TableInfoHelper.initTableInfo(assistant, modelClass);
        }
        return tableInfo;
    }
    
    /*private String generateResultMapName(Method method) {
        Results results = method.getAnnotation(Results.class);
        if (results != null && !results.id().isEmpty()) {
          return type.getName() + "." + results.id();
        }
        StringBuilder suffix = new StringBuilder();
        for (Class<?> c : method.getParameterTypes()) {
          suffix.append("-");
          suffix.append(c.getSimpleName());
        }
        if (suffix.length() < 1) {
          suffix.append("-void");
        }
        return type.getName() + "." + method.getName() + suffix;
      }*/
}
